﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace Gamey
{
    public class MameXmlLoader
    {
        static readonly string GameListFilePath;
        static readonly string MammyDataFilePath;
        static readonly Regex LocaleMatcher;
        static readonly Dictionary<string, MameLocale> LocaleLookup;

        static MameXmlLoader()
        {
            GameListFilePath = Path.Combine(Env.GameyDirectoryPath, "mamedata.xml");
            MammyDataFilePath = Path.Combine(Env.GameyDirectoryPath, "mammydata.dat");

            LocaleMatcher = new Regex(@"\(([a-z]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
            LocaleLookup = new Dictionary<string, MameLocale>(StringComparer.InvariantCultureIgnoreCase)
            {
                { "world", MameLocale.World },
                { "asia", MameLocale.Asia },
                { "usa", MameLocale.USA },
                { "us", MameLocale.USA },
                { "euro", MameLocale.Europe },
                { "eur", MameLocale.Europe },
                { "japan", MameLocale.Japan },
                { "jpn", MameLocale.Japan },
                { "korea", MameLocale.Korea },
                { "taiwan", MameLocale.Taiwan },
                { "hong", MameLocale.HongKong },
                { "russia", MameLocale.Russia },
                { "germany", MameLocale.Germany },
                { "france", MameLocale.France },
                { "brazil", MameLocale.Brazil }
            };
        }

        public void LoadGames(GameyDataSet.GamesDataTable table)
        {
            if (!TryLoadBinaryDataSet(table))
            {
                LoadXmlDataSet(table);
            }

            MarkPresentGames(table);
        }

        bool TryLoadBinaryDataSet(GameyDataSet.GamesDataTable table)
        {
            if (!File.Exists(MammyDataFilePath))
            {
                return false;
            }

            try
            {
                GameyDataSet serializedDataSet;
                using (Stream stream = File.OpenRead(MammyDataFilePath))
                {
                    serializedDataSet = (GameyDataSet)new BinaryFormatter().Deserialize(stream);
                }
                PopulateTableFromDataSet(serializedDataSet, table);

                return true;
            }
            catch
            {
                return false;
            }
        }

        void PopulateTableFromDataSet(GameyDataSet dataSet, GameyDataSet.GamesDataTable table)
        {
            IDataReader dataReader = dataSet.Games.CreateDataReader();
            table.Load(dataReader);
        }

        void LoadXmlDataSet(GameyDataSet.GamesDataTable table)
        {
            XDocument document = LoadGameListXml();
            PopulateTableFromDocument(document, table);
        }

        XDocument LoadGameListXml()
        {
            if (File.Exists(GameListFilePath))
            {
                using (StreamReader streamReader = File.OpenText(GameListFilePath))
                {
                    return XDocument.Load(streamReader);
                }
            }

            return CreateGameListXmlFromMameExecutable();
        }

        XDocument CreateGameListXmlFromMameExecutable()
        {
            var startInfo = new ProcessStartInfo
            {
                FileName = Env.Settings.MamePath,
                Arguments = "-listxml",
                RedirectStandardOutput = true,
                UseShellExecute = false,
                CreateNoWindow = true
            };

            XDocument document;
            using (Process mame = Process.Start(startInfo))
            {
                document = XDocument.Load(mame.StandardOutput);
            }

            document.Save(GameListFilePath);

            return document;
        }

        void PopulateTableFromDocument(XDocument document, GameyDataSet.GamesDataTable table)
        {
            foreach (XElement element in document.Root.Elements("game"))
            {
                string name;
                string description;

                if (!TryReadAttribute(element, "name", out name) || !TryReadElement(element, "description", out description))
                {
                    continue;
                }

                GameyDataSet.GamesRow row = table.NewGamesRow();
                row.Name = name;
                row.Description = description;

                string rawYear;
                int year;
                if (TryReadElement(element, "year", out rawYear) && int.TryParse(rawYear, out year))
                {
                    row.Year = year;
                }

                Match localeMatch = LocaleMatcher.Match(description);
                MameLocale locale;
                if (localeMatch.Success && LocaleLookup.TryGetValue(localeMatch.Groups[1].Value, out locale))
                {
                    row.Locale = locale.ToString();
                }

                string manufacturer;
                if (TryReadElement(element, "manufacturer", out manufacturer))
                {
                    row.Manufacturer = manufacturer;
                }

                string rawPlayers;
                int players;
                if (TryReadAttribute(element.Element("input"), "players", out rawPlayers) && int.TryParse(rawPlayers, out players))
                {
                    row.Players = players;
                }

                table.AddGamesRow(row);
            }

            using (Stream stream = File.OpenWrite(MammyDataFilePath))
            {
                new BinaryFormatter().Serialize(stream, table.DataSet);
            }
        }

        void MarkPresentGames(GameyDataSet.GamesDataTable table)
        {
            var romsDirectory = new DirectoryInfo(Env.Settings.RomsPath);
            foreach (FileInfo romFile in romsDirectory.GetFiles("*.zip"))
            {
                string fileName = Path.GetFileNameWithoutExtension(romFile.Name);
                GameyDataSet.GamesRow row = table.FindByName(fileName);
                if (row != null)
                {
                    row.Present = true;
                    row.AcceptChanges();
                }
            }
        }

        bool TryReadAttribute(XElement element, string attributeName, out string value)
        {
            value = null;

            if (element == null)
            {
                return false;
            }

            XAttribute attribute = element.Attribute(attributeName);
            if (attribute == null)
            {
                return false;
            }

            value = attribute.Value;

            return true;
        }

        bool TryReadElement(XElement element, string elementName, out string value)
        {
            value = null;

            XElement childElement = element.Element(elementName);
            if (childElement == null)
            {
                return false;
            }

            value = childElement.Value;

            return true;
        }
    }
}
